home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Best of MacTutor - S…e Code for Volumes 1 to 5
/
The Best of MacTutor - Source Code for Volume 1-5 (Wayzata Technology)(6031)(1990).bin
/
Source Code
/
#43 (Apr 89)
/
ScrollMenu Source
/
scrollMenuBarPatch.c
< prev
next >
Wrap
C/C++ Source or Header
|
1987-11-22
|
12KB
|
453 lines
/* scrollMenuBarPatch.c 18 Nov 1987
*
* by Mike Scanlin and Andy Voelker
*
* This will install a patch to _GetNextEvent that intercepts mouseDown events if they occur
* in the very top pixel-row of the screen. If an event is intercepted, the menu bar is scrolled
* off the right side of the screen before the user can react. After 4 more clicks in the menuBar
* (with appropriately sarcastic messages displayed after each one) the original menuBar is
* restored. The patch can be removed by typing COMMAND-OPTION-SHIFT-TAB.
*/
#include "EventMgr.h"
#include "QuickDraw.h"
#include "asm.h"
/* low memory globals */
extern Handle MenuList : 0x0A1C;
extern Ptr ScrnBase : 0x0824;
extern GrafPtr WMgrPort : 0x09DE;
/* the traps we patch */
#define GetNextEventTrap 0xA970
#define DrawMenuBarTrap 0xA937
#define HiliteMenuTrap 0xA938
#define CLICKS_TO_MENU 4
#define MESSAGE_DELAY 60
#define MENU_BAR_HEIGHT 20
#define TAB_KEY 0x09
#define JMP_INSTRUCTION 0x4EF9
#define memFullErr -108
#define screenBits_bounds_right -110
#define screenBits_bounds_left -114
void main(void);
void main()
{
asm {
/* This first section is the only part that gets run initially. It gets some space in the system heap
* and sets up a patch to _GetNextEvent. The patch won't do anything until the user clicks in the
* very top pixel of the menu bar (in which case it will scroll the menu bar off the screen to
* the right). */
move.l D3,-(SP)
/* get the old trap address */
move #GetNextEventTrap,D0
_GetTrapAddress
/* set the address for the JMP instruction that calls the original trap */
lea @origTrap,A1
move.l A0,(A1)
/* get some space in the system heap for our patch */
lea @last,A0
lea @first,A1
suba.l A1,A0
move.l A0,D0 /* D0 = length of patch */
move.l D0,D3 /* save for _BlockMove */
_NewPtr SYS
cmpi #memFullErr,D0
beq.s @noPatch
lea @saveLoc,A1
move.l A0,(A1) /* save for removePatch */
move.l A0,-(SP) /* save for _BlockMove */
/* set the trap address to the space we just got in the system heap. */
move #GetNextEventTrap,D0
_SetTrapAddress
/* now move our patch into place */
lea @first,A0
move.l (SP)+,A1 /* result from _NewPtr */
move.l D3,D0
_BlockMove
@noPatch
move.l (SP)+,D3
/* this is the end of the installation part, but we can't do an
* RTS here because LSC needs to clean up. So we fall through
* to the end. */
bra @last
/**********************************************
* Here's the new _GetNextEvent. It calls the existing _GetNextEvent
* and then checks if a mouseDown or keyDown event is being reported.
* If not, the event is passed to the application unmodified. If we end up
* using the event ourselves, a null event is returned to the application.
**********************************************/
@first
/* pop the original return address and save it */
lea @exitAddress,A0
move.l (SP)+,(A0)
/* save ptr to event record so we can get at it later */
lea @eventRecPtr,A0
move.l (SP),(A0)
/* set the return address to our patch */
pea @tailPatch
/* the nops get filled with the address of the original _GetNextEvent */
dc JMP_INSTRUCTION
@origTrap
nop
nop
/* this is where it comes after the normal _GetNextEvent processing */
@tailPatch
movem.l A1/D0-D3,-(SP)
lea @eventRecPtr,A0
move.l (A0),A0
/* check if it's a keydown event that says to remove ourself. This is the only keyDown
* that we intercept. */
move OFFSET(EventRecord,what)(A0),D0
cmpi #keyDown,D0
bne.s @noKeyDown
/* the key to remove the patch is COMMAND-SHIFT-OPTION-Tab */
move.l OFFSET(EventRecord,message)(A0),D0
cmpi.b #TAB_KEY,D0
bne.s @noKeyDown
move OFFSET(EventRecord,modifiers)(A0),D0
andi #optionKey + cmdKey + shiftKey,D0
eori #optionKey + cmdKey + shiftKey,D0
beq.s @removePatch
@noKeyDown
/* if it's not a mousedown event, then ignore it */
cmpi #mouseDown,D0
bne.s @patchExit
/* if we've already scrolled the menu list, then don't scroll it again */
lea @menus,A1
tst (A1)
bne.s @alreadyGone
/* if no menus exist, then leave */
move.l MenuList,A1
move.l (A1),A1
tst (A1)
beq.s @patchExit
/* if the mouse is not at an odd location, then leave */
move.l OFFSET(EventRecord,where)(A0),D0
andi #1,D0
beq.s @patchExit
/* if the mouse is not at the very top pixel of the menu bar, then leave */
move.l OFFSET(EventRecord,where)(A0),D0
/* put vertical coordinate in low word */
swap D0
cmpi #1,D0
bge.s @patchExit
/* now we're set to scroll that puppy. Do it... */
bsr @scrollMenuBar
/* save the fact that the menu bar that was just scrolled */
lea @menus,A0
move #1,(A0)
/* patch _DrawMenuBar and _HiliteMenu to do nothing if and when they're called */
bsr @disableTraps
lea @clicks,A1
move #CLICKS_TO_MENU,(A1)
bra.s @returnNullEvent
@alreadyGone
/* if mouse is not in menu bar, then leave */
move.l OFFSET(EventRecord,where)(A0),D0
/* put vertical coordinate in low word */
swap D0
cmpi #MENU_BAR_HEIGHT,D0
bge.s @patchExit
/* print a message in the menu bar */
bsr @drawAMessage
lea @clicks,A0
subi #1,(A0)
bne.s @returnNullEvent
/* now that we're finished playing, restore _DrawMenuBar and _HiliteMenu */
bsr @restoreMenus
@returnNullEvent
/* set the event to null */
lea @eventRecPtr,A0
move.l (A0),A0
clr OFFSET(EventRecord,what)(A0)
/* change _GetNextEvent's return value to false.
* the 20 is for the 5 regs that are saved on the stack at this point */
clr 20(SP)
@patchExit
movem.l (SP)+,A1/D0-D3
/* JMP to the place that called _GetNextEvent */
dc JMP_INSTRUCTION
@exitAddress
nop
nop
/* this is where it comes to remove the _GetNextEvent patch */
@removePatch
/* if the menus aren't shown, then restore them before leaving */
bsr @restoreMenus
/* check if we are the most recent patch to _GetNextEvent.
* If we're not, then don't unpatch. */
move #GetNextEventTrap,D0
_GetTrapAddress
lea @first,A1
cmpa.l A1,A0
bne.s @returnNullEvent
/* set the trap address back to the original trap. */
lea @origTrap,A0
move.l (A0),A0
move #GetNextEventTrap,D0
_SetTrapAddress
/* beep to let them know it has been removed */
move #1,-(SP)
_SysBeep
/* free up the mem occupied by this patch */
lea @saveLoc,A0
move.l (A0),A0
_DisposPtr
bra.s @returnNullEvent
/* This routine will disable _DrawMenuBar and _HiliteMenu */
@disableTraps
move #DrawMenuBarTrap,D0
_GetTrapAddress
lea @drawMenuBarAddr,A1
move.l A0,(A1)
lea @doNothing,A0
move #DrawMenuBarTrap,D0
_SetTrapAddress
move #HiliteMenuTrap,D0
_GetTrapAddress
lea @hiliteMenuAddr,A1
move.l A0,(A1)
lea @doNothingWithParam,A0
move #HiliteMenuTrap,D0
_SetTrapAddress
rts
/* This routine will restore _DrawMenuBar and _HiliteMenu and
* then draw the current menu bar */
@restoreMenus
lea @menus,A0
tst (A0)
beq.s @doNothing
/* unpatch DrawMenuBar and then draw the old menu bar */
lea @drawMenuBarAddr,A0
move.l (A0),A0
move #DrawMenuBarTrap,D0
_SetTrapAddress
lea @hiliteMenuAddr,A0
move.l (A0),A0
move #HiliteMenuTrap,D0
_SetTrapAddress
lea @menus,A0
clr (A0)
_DrawMenuBar
@doNothing
rts
/* This is the _HiliteMenu routine that does nothing. It gets rid of the parameter passed
* to _HiliteMenu and then returns. */
@doNothingWithParam
move.l (SP)+,A0
addq.l #2,SP
jmp (A0)
/* This is the routine that actually does the scrolling */
@scrollMenuBar
/* hide the cursor so we don't get part of the cursor scrolled with the menuBar */
_HideCursor
/* get the current screen width from the Quickdraw global screenBits */
move.l (A5),A0
/* D3 = # of columns (width) - 1 in the current screen */
move screenBits_bounds_right(A0),D3
sub screenBits_bounds_left(A0),D3
move D3,D2
subq #1,D3
/* D2 = # of bytes - 1 in one row of screen */
asr #3,D2
subq #1,D2
/* here's the loop that actually scrolls the menu bar all the way across the screen */
@wayOut
move #MENU_BAR_HEIGHT - 2,D1
/* this outside loop will scroll all rows of the menu bar one pixel to the right */
@outside
move D2,D0
addq #1,D0
mulu D1,D0 /* calc # of bytes from base addr to start of current row */
move.l ScrnBase,A0
adda.l D0,A0
clr.b (A0) /* white-out the left edge of this row */
move D2,D0 /* D0 = number of words in one row */
asr #1,D0
andi.b #0xEF,CCR /* set the X flag */
/* this inner loop will scroll one row of the screen one pixel to the right */
@inside
roxr (A0)+
dbra D0,@inside
/* go and do the next row */
dbra D1,@outside
/* make the corners look like they used to (i.e. rounded and black) */
move D2,D0
move.l ScrnBase,A1
ori.b #0xF8,(A1)
adda D0,A1
ori.b #0x1F,(A1)+
ori.b #0xE0,(A1)
adda D0,A1
ori.b #0x07,(A1)+
ori.b #0xC0,(A1)
adda D0,A1
ori.b #0x03,(A1)+
ori.b #0x80,(A1)
adda D0,A1
ori.b #0x01,(A1)+
ori.b #0x80,(A1)
adda D0,A1
ori.b #0x01,(A1)
dbra D3,@wayOut
_ShowCursor
rts
/* this routine will print a string in the menu bar, wait a bit and then erase it */
@drawAMessage
/* set the current port to the window manager's */
move.l (A5),A0
lea @savePort,A1
move.l (A0),(A1)
move.l WMgrPort,(A0)
/* save the old clipRgn */
lea @saveClip,A0
move.l WMgrPort,A1
move.l OFFSET(GrafPort,clipRgn)(A1),(A0)
/* set the clipRgn to be big enough for the string we want to print */
subq #4,SP
_NewRgn
move.l WMgrPort,A1
move.l (SP)+,OFFSET(GrafPort,clipRgn)(A1)
pea @stringRect
_ClipRect
/* the string we print depends on the value of the clicks variable */
lea @clicks,A0
move (A0),D0
cmpi #4,D0
bne.s @1
pea @string1
bra.s @5
@1 cmpi #3,D0
bne.s @2
pea @string2
bra.s @5
@2 cmpi #2,D0
bne.s @3
pea @string3
bra.s @5
@3 cmpi #1,D0
bne.s @delay
pea @string4
@5 move #10,-(SP)
move #14,-(SP)
_MoveTo
_DrawString
@delay
lea @downTime,A0
move.l Ticks,(A0)
/* wait until the mouse is released or 60 ticks, which ever is longer */
@waitTilMouseUp
subq.l #2,SP
_StillDown
tst (SP)+
bne.s @waitTilMouseUp
lea @downTime,A0
move.l (A0),D0
addi.l #MESSAGE_DELAY,D0
cmp.l Ticks,D0
bgt.s @waitTilMouseUp
/* erase the string */
pea @stringRect
_EraseRect
/* dispose of the clipRgn we created */
move.l WMgrPort,A1
move.l OFFSET(GrafPort,clipRgn)(A1),-(SP)
_DisposeRgn
/* restore the old clip rgn */
lea @saveClip,A0
move.l WMgrPort,A1
move.l (A0),OFFSET(GrafPort,clipRgn)(A1)
/* restore the old port */
move.l (A5),A0
lea @savePort,A1
move.l (A1),(A0)
rts
@eventRecPtr dc.l 0
@menus dc 0 /* TRUE while menus that exist are not being shown */
@downTime dc.l 0
@clicks dc 0 /* number of menus clicks until menus return */
@words dc 0 /* number of 16 bit words in one row of screen */
@saveLoc dc.l 0 /* address of our patch in the system heap */
@savePort dc.l 0
@saveClip dc.l 0
@drawMenuBarAddr dc.l 0
@hiliteMenuAddr dc.l 0
/* this rect should be big enough to enclose the longest of the strings. */
@stringRect dc 0,8,MENU_BAR_HEIGHT - 1,300
/* define the pascal strings. Does LSC provide a better way than this? */
@string1 dc.b 13,'N','o',' ','M','e','n','u','s',' ','H','e','r','e'
@string2 dc.b 14,'S','t','i','l','l',' ','N','o',' ','M','e','n','u','s'
@string3 dc.b 7,'N','o','t',' ','Y','e','t'
@string4 dc.b 25,'W','h','e','r','e',' ','D','i','d',' ','T','h','o','s','e'
dc.b ' ','M','e','n','u','s',' ','G','o','?'
@last
}
}